package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mq.msg.SignInMessage;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;


    @Override
    public SignResultVO addSignRecords() {
        // 1. 获取用户id
        Long userId = UserContext.getUser();

        // 2. 拼接 key
        LocalDate now = LocalDate.now(); // 当前时间的年月
        String time = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId.toString() + time;

        // 3. 利用 bitset 命令, 将签到记录保存到 redis(true 已签到, 就不签到)
        int offset = now.getDayOfMonth() - 1;
        Boolean setBit = redisTemplate.opsForValue().setBit(key, offset, true);
        if (setBit) {
            // 已经签到
            throw new BizIllegalException("不能连续签到");
        }

        // 4. 计算连续签到天数
        int days = countSignDays(key, now.getDayOfMonth());

        // 5. 计算连续签到的奖励积分
        int reward = 0;
        switch (days){
            case 7:
                reward = 10;
                break;
            case 14:
                reward = 20;
                break;
            case 40:
                reward = 40;
                break;
        }

        // 6. 保存积分(发送消息到mq)
        mqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId, reward + 1)
        );

        // 7. 封装vo返回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(days);
        vo.setRewardPoints(reward);

        return vo;
    }

    // 计算连续签到多少天
    private int countSignDays(String key, int dayOfMonth) {
        // 1. 求本月第一天到今天的签到数据
        List<Long> bitField = redisTemplate.opsForValue().bitField(key,
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(bitField)) {
            return 0;
        }
        Long num = bitField.get(0); // 本月第一天到今天的签到数据

        // 2. 计算
        int count = 0;
        while ((num & 1) == 1) {
            count++;
            num >>= 1;
        }

        return count;
    }

    @Override
    public Byte[] querySignRecords() {
        // 1. 获取用户id
        Long userId = UserContext.getUser();

        // 2. 拼接 key
        LocalDate now = LocalDate.now(); // 当前时间的年月
        String time = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId.toString() + time;

        // 3. 利用 bitfield 取本月签到记录
        int dayOfMonth = now.getDayOfMonth(); // 本月到今天的天数

        List<Long> list = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );

        if (CollUtils.isEmpty(list)) {
            return new Byte[0];
        }

        // 4. 利用 & >> 取二进制每一位
        Long num = list.get(0); // 通过 bitfield 查的只有一个结果(十进制)
        int offset = dayOfMonth - 1;
        Byte[] arr = new Byte[dayOfMonth];

        while (offset >= 0) {
            arr[offset] = (byte) (num & 1);
            num >>= 1;
            offset--;
        }

        return arr;
    }

}
